package com.wizitsoft.com.lab.ast;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty;

public class DomainExtClassInjector {
	public static final String[] CLOSURES = {
			GrailsDomainClassProperty.CONSTRAINTS,
			GrailsDomainClassProperty.MAPPING };
	public static final String[] MAPS = { GrailsDomainClassProperty.HAS_MANY,
			GrailsDomainClassProperty.MAPPED_BY,
			GrailsDomainClassProperty.BELONGS_TO };
	public static final String[] OBJECTS = { GrailsDomainClassProperty.BELONGS_TO };
	public static final String[] ARRAYS = { GrailsDomainClassProperty.EMBEDDED,
			GrailsDomainClassProperty.BELONGS_TO };

	public void performInjectionOnDomainExt(ClassNode extNode) {
		System.out.println("[performInjectionOnDomainExt]: ");
		List annotations = extNode
				.getAnnotations(new ClassNode(DomainExt.class));
		for (Iterator iterator = annotations.iterator(); iterator.hasNext();) {
			AnnotationNode annotationNode = (AnnotationNode) iterator.next();
			ClassNode domainNode = annotationNode.getMember("value").getType();
			injectExtToDomain(extNode, domainNode);
			System.out.println("domain = " + domainNode);
		}
	}

	private void injectExtToDomain(ClassNode extNode, ClassNode domainNode) {
		System.out.println("[injectExtToDomain]: ");
		injectProperties(extNode, domainNode);
		injectClosures(extNode, domainNode);
		injectMaps(extNode, domainNode);
		injectObjects(extNode, domainNode);
		injectLists(extNode, domainNode);
	}

	private void injectLists(ClassNode extNode, ClassNode domainNode) {
		System.out.println("[injectLists]: ");
		for (int i = 0; i < ARRAYS.length; i++) {
			PropertyNode sourceNode = extNode.getProperty(ARRAYS[i]);
			if (sourceNode != null) {
				Expression sourceExpr = sourceNode.getInitialExpression();
				System.out.println("[injectLists]: sourceExpr: " + sourceExpr);
				if (sourceExpr instanceof ListExpression) {
					PropertyNode destNode = domainNode.getProperty(ARRAYS[i]);
					if (destNode == null) {
						domainNode.addProperty(sourceNode);
					} else {
						Expression destExpr = destNode.getInitialExpression();
						System.out.println("[injectLists]: destExpr: " + destExpr);
						if (destExpr instanceof ListExpression) {
							List sourceArrayExpressions = ((ListExpression) sourceExpr)
									.getExpressions();
							if (sourceArrayExpressions != null
									&& sourceArrayExpressions.size() > 0) {
								System.out
										.println("[injectLists]: sourceArrayExpressions size: "
												+ sourceArrayExpressions.size());
								List destArrayExpressions = ((ListExpression) destExpr)
										.getExpressions();
								System.out
										.println("[injectLists]: before destArrayExpressions size: "
												+ destArrayExpressions.size());
								destArrayExpressions
										.addAll(sourceArrayExpressions);
								System.out
										.println("[injectLists]: after destArrayExpressions size: "
												+ destArrayExpressions.size());
							}
						}
					}
				}
			}
		}
	}

	private void injectObjects(ClassNode extNode, ClassNode domainNode) {
		System.out.println("[injectObjects]: ");
		for (int i = 0; i < OBJECTS.length; i++) {
			PropertyNode sourceNode = extNode.getProperty(OBJECTS[i]);
			if (sourceNode != null) {
				Expression sourceExpr = sourceNode.getInitialExpression();
				System.out.println("[injectObjects]: sourceExpr: " + sourceExpr);
				if (sourceExpr instanceof ClassExpression) {
					PropertyNode destNode = domainNode.getProperty(OBJECTS[i]);
					if (destNode == null) {
						domainNode.addProperty(sourceNode);
					} else {
						Expression destExpr = destNode.getInitialExpression();
						System.out.println("[injectObjects]: destExpr: " + destExpr);
						System.out
								.println("[injectObjects]: Can't override the existing property!");
					}
				}
			}
		}
	}

	private void injectMaps(ClassNode extNode, ClassNode domainNode) {
		System.out.println("[injectMaps]: ");
		for (int i = 0; i < MAPS.length; i++) {
			PropertyNode sourceNode = extNode.getProperty(MAPS[i]);
			if (sourceNode != null) {
				Expression sourceExpr = sourceNode.getInitialExpression();
				System.out.println("[injectMaps]: sourceExpr: " + sourceExpr);
				if (sourceExpr instanceof MapExpression) {
					PropertyNode destNode = domainNode.getProperty(MAPS[i]);
					if (destNode == null) {
						domainNode.addProperty(sourceNode);
						System.out.println("[injectMaps]: added node: "
								+ domainNode.getProperty(MAPS[i]));
					} else {
						Expression destExpr = destNode.getInitialExpression();
						System.out.println("[injectMaps]: befor injecting destExpr: "
								+ destExpr);
						if (destExpr instanceof MapExpression) {
							List sourceMapEntries = ((MapExpression) sourceExpr)
									.getMapEntryExpressions();
							System.out.println("[injectMaps]: sourceMapEntries size:"
									+ sourceMapEntries.size());
							if (sourceMapEntries != null
									&& sourceMapEntries.size() > 0) {
								List destMapEntries = ((MapExpression) destExpr)
										.getMapEntryExpressions();

								if (destMapEntries == null
										|| destMapEntries.size() < 1) {
									domainNode.addProperty(sourceNode);
								} else {
									System.out.println("[injectMaps]: destMapEntries size:"
											+ destMapEntries.size());
									destMapEntries.addAll(sourceMapEntries);
									System.out.println("[injectMaps]: destMapEntries size:"
											+ destMapEntries.size());
									destExpr = new MapExpression(destMapEntries);
									if (sourceMapEntries != null) {
										for (int j = 0; j < sourceMapEntries
												.size(); j++) {
											MapEntryExpression mapEntryExpr = (MapEntryExpression) sourceMapEntries
													.get(j);
											System.out
													.println("[injectMaps]: map entry key: "
															+ mapEntryExpr
																	.getKeyExpression());
											// ((MapExpression) destExpr)
											// .addMapEntryExpression(mapEntryExpr);

										}
									}
								}
							}
						}
						System.out.println("[injectMaps]: after injection destExpr: "
								+ destExpr);
					}
				}
			}
		}

	}

	private void injectClosures(ClassNode extNode, ClassNode domainNode) {
		System.out.println("[injectClosures]: ");
		for (int i = 0; i < CLOSURES.length; i++) {

			PropertyNode sourceNode = extNode.getProperty(CLOSURES[i]);
			System.out.println("[injectClosures]: sourceNode: " + sourceNode);
			if (sourceNode != null) {
				PropertyNode destNode = domainNode.getProperty(CLOSURES[i]);
				if (destNode == null) {
					domainNode.addProperty(sourceNode);
				} else {
					extendPropertyNode(sourceNode, destNode);
				}
			}
		}
	}

	private void extendPropertyNode(PropertyNode sourceNode,
			PropertyNode destNode) {
		System.out.println("[extendPropertyNode]: ");
		Expression sourceExpr = sourceNode.getInitialExpression();
		if (sourceExpr instanceof ClosureExpression) {
			Expression destExpr = destNode.getInitialExpression();
			if (destExpr instanceof ClosureExpression) {
				Statement sourceStatement = ((ClosureExpression) sourceExpr)
						.getCode();
				Statement destStatement = ((ClosureExpression) destExpr)
						.getCode();
				if (sourceStatement instanceof BlockStatement
						&& destStatement instanceof BlockStatement) {
					addNewStatements(sourceStatement, destStatement);
				}
			}
		}
	}

	private void addNewStatements(Statement sourceStatement,
			Statement destStatement) {
		System.out.println("[addNewStatements]: ");
		List newStatements = ((BlockStatement) sourceStatement).getStatements();
		List existingStatements = ((BlockStatement) destStatement)
				.getStatements();
		System.out.println("[addNewStatements]: ==========before==================");
		System.out.println(sourceStatement.getText());
		System.out.println("[addNewStatements]: ============================");
		System.out.println(destStatement.getText());
		System.out.println("[addNewStatements]: ============================");
		if (newStatements != null) {
			for (Iterator itr = newStatements.iterator(); itr.hasNext();) {
				existingStatements.add(itr.next());
			}
		}
		System.out.println("[addNewStatements]: ============after================");
		System.out.println(sourceStatement.getText());
		System.out.println("[addNewStatements]: ============================");
		System.out.println(destStatement.getText());
		System.out.println("[addNewStatements]: ============================");

	}

	private void injectProperties(ClassNode extNode, ClassNode domainNode) {
		System.out.println("[injectProperties]: ");

		List properties = extNode.getProperties();
		List extProperties = findExtProperties(properties);
		for (Iterator iterator = extProperties.iterator(); iterator.hasNext();) {
			PropertyNode propertyNode = (PropertyNode) iterator.next();

			domainNode.addProperty(propertyNode);
		}
	}

	private List findExtProperties(List properties) {
		System.out.println("[findExtProperties]: ");

		List result = new ArrayList();
		if (properties != null) {
			for (Iterator iterator = properties.iterator(); iterator.hasNext();) {
				PropertyNode propertyNode = (PropertyNode) iterator.next();
				System.out.println("[findExtProperties]: propertyNode = " + propertyNode.getName());
				if (isExtensionProperty(propertyNode)) {
					result.add(propertyNode);
				}
			}
		}

		return result;
	}

	private boolean isExtensionProperty(PropertyNode propertyNode) {
		System.out.println("[isExtensionProperty]: ");
		List annotations = propertyNode.getField().getAnnotations(
				new ClassNode(PropertyExt.class));
		boolean result = hasPropertyExtAnnotation(annotations);
		if (!result) {
			annotations = propertyNode.getAnnotations(new ClassNode(
					PropertyExt.class));
			result = hasPropertyExtAnnotation(annotations);
		}
		return result;
	}

	private boolean hasPropertyExtAnnotation(List annotations) {
		System.out.println("[hasPropertyExtAnnotation]: ");
		for (Iterator iterator = annotations.iterator(); iterator.hasNext();) {
			AnnotationNode annotationNode = (AnnotationNode) iterator.next();
			String cName = annotationNode.getClassNode().getName();

			System.out.println("[hasPropertyExtAnnotation]: cName = " + cName);

			if (PropertyExt.class.getName().equals(cName)) {
				System.out
						.println("[hasPropertyExtAnnotation]: got com.wizitsoft.com.lab.ast.PropertyExt!");
				return true;
			}
		}
		return false;
	}
}
